"""
环境配置生成器.

从 app/core/config.py 的 Settings 类自动生成 .env.example 环境变量模板文件。

使用方法:
    uv run -m scripts.generate_env [OPTIONS]

示例:
    uv run -m scripts.generate_env
    uv run -m scripts.generate_env --output .env.template
    uv run -m scripts.generate_env --check
"""

import argparse
import importlib
import sys
from collections import defaultdict
from pathlib import Path
from typing import Any

import orjson
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings


class ConfigField(BaseModel):
    """配置字段数据模型."""

    name: str = Field(description="字段名称(环境变量名)")
    field_type: Any = Field(default=None, description="字段类型")
    default_value: Any = Field(default=None, description="默认值")
    description: str = Field(default="配置项", description="字段描述")
    tag: str = Field(default="默认配置", description="分组标签")
    is_sensitive: bool = Field(default=False, description="是否敏感字段")
    order: int = Field(default=0, description="定义顺序")


class ConfigGroup(BaseModel):
    """配置分组数据模型."""

    tag: str = Field(description="分组标签")
    fields: list[ConfigField] = Field(description="属于该分组的字段列表")
    order: int = Field(description="显示顺序")


class EnvVariable(BaseModel):
    """环境变量条目数据模型."""

    key: str = Field(description="环境变量键")
    value: str = Field(description="环境变量值(字符串化)")
    comment: str = Field(default="", description="行注释(描述)")
    is_sensitive: bool = Field(default=False, description="是否敏感")


class EnvTemplate(BaseModel):
    """环境变量模板数据模型."""

    header: str = Field(description="文件头部注释")
    groups: list[ConfigGroup] = Field(description="配置分组列表")
    content: str = Field(description="最终文件内容")


def extract_fields(settings_class: type[BaseSettings]) -> list[ConfigField]:
    """
    从 Settings 类提取配置字段.

    Args:
        settings_class: Pydantic Settings 类

    Returns:
        ConfigField 列表

    Raises:
        RuntimeError: 如果未找到任何配置字段

    """
    fields: list[ConfigField] = []

    if not hasattr(settings_class, "model_fields"):
        msg = f"错误: {settings_class.__name__} 不是有效的 Pydantic Settings 类"
        raise RuntimeError(msg)

    for order, (name, field_info) in enumerate(settings_class.model_fields.items()):
        # 提取字段元数据
        description = field_info.description or "配置项"
        tag = "默认配置"
        is_sensitive = False

        # 从 metadata 中的 WithJsonSchema 获取标签
        if field_info.metadata:
            for metadata_item in field_info.metadata:
                if hasattr(metadata_item, "json_schema") and isinstance(
                    metadata_item.json_schema, dict
                ):
                    tag_value = metadata_item.json_schema.get("tag")
                    if tag_value:
                        tag = str(tag_value)
                        break

        # 检查敏感字段标记
        if hasattr(field_info, "repr") and field_info.repr is False:
            is_sensitive = True

        fields.append(
            ConfigField(
                name=name,
                field_type=field_info.annotation,
                default_value=field_info.default,
                description=description,
                tag=tag,
                is_sensitive=is_sensitive,
                order=order,
            )
        )

    if not fields:
        msg = "错误: 未找到任何配置字段"
        raise RuntimeError(msg)

    return fields


def format_value(value: object) -> str:
    """
    格式化字段值为 .env 中的 value 值.

    Args:
        value: 待格式化值

    Returns:
        格式化后的字符串值

    规则:
        - None: 空字符串
        - 字符串: 使用 shlex.quote 转义
        - bool, int, float: 转换为字符串
        - list, tuple, dict: 先反序列化，再调用一遍格式化方法
        - bytes: 解码为字符串，再调用一遍格式化方法
        - 其他: 先转换为字符串，再调用一遍格式化方法

    """
    match value:
        case None:
            return ""
        case str():
            if value.isalnum():
                return value
            else:
                return "'{}'".format(value.replace("'", "\\'"))
        case bool() | int() | float():
            return str(value)
        case list() | tuple() | dict():
            return format_value(orjson.dumps(value).decode("utf-8"))
        case bytes():
            return format_value(value.decode("utf-8"))
        case _:
            return format_value(str(value))


def group_by_tag(fields: list[ConfigField]) -> list[ConfigGroup]:
    """
    按标签分组配置字段.

    Args:
        fields: 配置字段列表

    Returns:
        ConfigGroup 列表(按定义顺序排序)

    """
    groups_dict: dict[str, list[ConfigField]] = defaultdict(list)

    for field in fields:
        tag = field.tag or "默认配置"
        groups_dict[tag].append(field)

    # 按每组首个字段的顺序排序
    sorted_groups = sorted(
        groups_dict.items(), key=lambda x: min(f.order for f in x[1])
    )

    return [
        ConfigGroup(
            tag=tag, fields=group_fields, order=min(f.order for f in group_fields)
        )
        for tag, group_fields in sorted_groups
    ]


def render_template(groups: list[ConfigGroup]) -> str:
    """
    渲染 .env.example 模板内容(无时间戳).

    Args:
        groups: 配置分组列表

    Returns:
        渲染后的模板内容

    """
    lines: list[str] = []

    # 文件头部(移除时间戳以减少 git diff 噪音)
    header = """# ===========================================
# .env.example - 环境变量配置模板
# 此文件由 scripts/generate_env.py 自动生成
# 请勿手动编辑, 所有修改将在下次生成时被覆盖
# ==========================================="""

    lines.append(header)

    # 渲染每个分组
    for group in groups:
        # 三行式分组头部
        lines.append(f"\n# {'=' * 40}")
        lines.append(f"# {group.tag}")
        lines.append(f"# {'=' * 40}")

        for field in group.fields:
            # 添加字段描述
            lines.append(f"# {field.description}")

            # 敏感字段警告
            if field.is_sensitive:
                lines.append("# [WARNING] 敏感信息, 请修改")

            # 字段键值对
            formatted_value = format_value(field.default_value)

            # None 值字段生成为注释形式
            if field.default_value is None:
                lines.append(f"# {field.name}=")
            else:
                lines.append(f"{field.name}={formatted_value}")

    return "\n".join(lines) + "\n"  # 添加末尾空行


def load_settings_class(import_path: str) -> type[BaseSettings]:
    """
    加载 Settings 类.

    Args:
        import_path: 导入路径, 格式为 "module.path:ClassName"

    Returns:
        Settings 类

    Raises:
        ImportError: 无法导入模块
        AttributeError: 类不存在

    """
    try:
        module_path, class_name = import_path.rsplit(":", 1)
    except ValueError as e:
        msg = f"错误: 无效的导入路径格式 '{import_path}', 应为 'module.path:ClassName'"
        raise ValueError(msg) from e

    try:
        module = importlib.import_module(module_path)
    except ImportError as e:
        file_path = f"{module_path.replace('.', '/')}.py"
        msg = (
            f"错误: 无法导入模块 '{module_path}'\n"
            f"  原因: {e}\n"
            f"  检查: {file_path} 是否存在"
        )
        raise ImportError(msg) from e

    try:
        settings_class = getattr(module, class_name)
    except AttributeError as e:
        msg = f"错误: 模块 '{module_path}' 中没有找到类 '{class_name}'"
        raise AttributeError(msg) from e

    if not isinstance(settings_class, type) or not issubclass(
        settings_class, BaseSettings
    ):
        msg = f"错误: {class_name} 不是 BaseSettings 的子类"
        raise TypeError(msg)

    return settings_class


def write_env_file(content: str, output_path: Path) -> None:
    """
    写入环境变量文件.

    Args:
        content: 文件内容
        output_path: 输出文件路径

    Raises:
        IOError: 无法写入文件

    """
    try:
        output_path.write_text(content, encoding="utf-8")
    except OSError as e:
        msg = f"错误: 无法写入文件 '{output_path}'\n  原因: {e}"
        raise OSError(msg) from e


def check_sync(content: str, output_path: Path) -> bool:
    """
    检查文件是否同步.

    Args:
        content: 生成的内容
        output_path: 现有文件路径

    Returns:
        True 如果同步, False 如果不同步

    """
    if not output_path.exists():
        return False

    existing_content = output_path.read_text(encoding="utf-8")
    return content == existing_content


def run_check_mode(content: str, output_path: Path, quiet: bool) -> None:
    """
    运行检查模式.

    Args:
        content: 生成的内容
        output_path: 输出文件路径
        quiet: 是否静默模式

    """
    if check_sync(content, output_path):
        if not quiet:
            print("[OK] .env.example 已同步")
        sys.exit(0)
    else:
        print("[ERROR] .env.example 与 config.py 不同步")
        print("\n运行 'make env-example' 更新模板")
        sys.exit(1)


def run_generate_mode(
    content: str, output_path: Path, fields: list, groups: list, quiet: bool
) -> None:
    """
    运行生成模式.

    Args:
        content: 生成的内容
        output_path: 输出文件路径
        fields: 字段列表
        groups: 分组列表
        quiet: 是否静默模式

    """
    write_env_file(content, output_path)

    if not quiet:
        print(f"[OK] 成功生成 {output_path}")
        print(f"   位置: {output_path.absolute()}")
        print(f"   字段数: {len(fields)}")
        print(f"   分组数: {len(groups)}")


def main() -> None:
    """主入口函数."""
    parser = argparse.ArgumentParser(
        description="从 Settings 类生成 .env.example 环境变量模板",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  uv run -m scripts.generate_env
  uv run -m scripts.generate_env --output .env.template
  uv run -m scripts.generate_env --check
  uv run -m scripts.generate_env --config myapp.config:Settings
        """,
    )

    parser.add_argument(
        "-o",
        "--output",
        type=Path,
        default=Path(".env.example"),
        help="输出文件路径 (默认: .env.example)",
    )
    parser.add_argument(
        "-c",
        "--config",
        type=str,
        default="app.core.config:Settings",
        help="配置类导入路径 (默认: app.core.config:Settings)",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        help="静默模式(仅输出错误)",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="检查模式(验证是否同步, 不写入文件)",
    )

    args = parser.parse_args()

    try:
        settings_class = load_settings_class(args.config)
        fields = extract_fields(settings_class)
        groups = group_by_tag(fields)
        content = render_template(groups)

        if args.check:
            run_check_mode(content, args.output, args.quiet)
        else:
            run_generate_mode(content, args.output, fields, groups, args.quiet)

    except (ImportError, AttributeError, TypeError, ValueError) as e:
        print(f"{e}", file=sys.stderr)
        sys.exit(1)
    except SyntaxError as e:
        print("[ERROR] config.py 存在语法错误", file=sys.stderr)
        print(f"   位置: 第 {e.lineno} 行, 第 {e.offset} 列", file=sys.stderr)
        if e.text:
            print(f"   内容: {e.text.strip()}", file=sys.stderr)
        print(f"   原因: {e.msg}", file=sys.stderr)
        sys.exit(2)
    except RuntimeError as e:
        print(f"[ERROR] {e}", file=sys.stderr)
        sys.exit(5)
    except OSError as e:
        print(f"[ERROR] {e}", file=sys.stderr)
        sys.exit(4)
    except Exception as e:
        print(f"[ERROR] 未预期的错误: {e}", file=sys.stderr)
        sys.exit(99)


if __name__ == "__main__":
    main()
